﻿// Spun AJAX Framework
// Copyright (C) 2008 Blake Hegerle
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Formality.Parsing.ContextFree;
using System.IO;

namespace Formality.Parsing.LexicalAnalysis
{
    public class LexerException : Exception
    {
        public LexerException(string BadText)
            : base("Can't tokenize " + BadText)
        { }
    }

    public class Lexer : IEnumerable<Token>
    {
        internal Lexer(TextReader Reader, IEnumerable<Rule> Rules)
        {
            this.Reader = Reader;
            this.Rules = Rules;
        }

        public IEnumerator<Token> GetEnumerator()
        {
            Buffer = "";
            Candidates = Rules;

            int Next;
            while ((Next = Reader.Read()) != -1)
            {
                var c = (char)Next;

                if (char.IsWhiteSpace(c))
                    continue;

                var NewBuffer = Buffer + c;

                var AppendedCandidates = from R in Candidates
                                         where R.Match.StartsWith(NewBuffer)
                                         select R;

                if (AppendedCandidates.Count() == 0)
                {
                    yield return Emit();

                    Buffer = c.ToString();
                    Candidates = Rules;
                }
                else
                {
                    Buffer = NewBuffer;
                    Candidates = AppendedCandidates;
                }
            }

            yield return Emit();
        }

        private Token Emit()
        {
            var Emit = (from C in Candidates
                        where C.Match == Buffer
                        select C).FirstOrDefault();

            if (Emit == null)
                throw new Exception("Not a token: '" + Buffer + "' "+Escape(Buffer));
            else
                return new Token
                {
                    Symbol = Emit.Terminal,
                    Value = Emit.Value
                };
        }

        private string Escape(string Buffer)
        {
            return (from c in Buffer select string.Format("\\u{0:x4}", (int)c)).Join("");
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); }

        public override string ToString()
        {
            var Buffer = new StringBuilder();

            foreach (var Rule in Rules)
                Buffer.AppendFormat("{0} matching {1} -> {2}\n", Rule.Terminal.Name, Rule.Match, Rule.Value);

            return Buffer.ToString();
        }

        internal class Rule
        {
            internal string Value;
            internal string Match;
            internal Terminal Terminal;
        }

        private TextReader Reader;
        private IEnumerable<Rule> Rules, Candidates;
        private string Buffer;
    }
}
